﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Manso.cBot.Common.Acl;
using VhaBot.Common;
using VhaBot.Net;
using VhaBot.Net.Events;

namespace Manso.cBot.Common
{
    /// <summary>
    /// Singleton class core of cBot
    /// </summary>
    public class cBotContext
    {
        #region Static stuff
        public static cBotContext Instance
        {
            get
            {
                if (_cBotContext == null)
                {
                    _cBotContext = new cBotContext();
                }
                return _cBotContext;
            }
        }
        private static cBotContext _cBotContext;
        #endregion

        #region Private
        private Chat _chat;
        private ModuleManager _moduleManager;
        private AccessControl _accessControl;
        private String _userName;
        private String _password;
        private String _characterName;
        private String _serverAddress;
        private int _port;
        private BigInteger _guildChannelID;
        #endregion

        #region Public properties
        /// <summary>
        /// Gets or sets account name
        /// </summary>
        public String UserName
        {
            get { return _userName; }
        }
        /// <summary>
        /// Gets or sets account password
        /// </summary>
        public String Password
        {
            get { return _password; }
        }
        /// <summary>
        /// Gets or sets character name
        /// </summary>
        public String CharacterName
        {
            get { return _characterName; }
        }
        /// <summary>
        /// Gets or sets server address
        /// </summary>
        public String ServerAddress
        {
            get { return _serverAddress; }
        }
        /// <summary>
        /// Gets or sets server port
        /// </summary>
        public int Port
        {
            get { return _port; }
        }
        /// <summary>
        /// Gets module manager
        /// </summary>
        public ModuleManager ModuleManager
        {
            get { return _moduleManager; }
        }
        /// <summary>
        /// Gets access control list
        /// </summary>
        public AccessControl AccessControl
        {
            get
            {
                return _accessControl;
            }
        }
        #endregion

        #region Constructor
        private cBotContext()
        {
        }
        #endregion

        #region Exposed events
        public event FriendStatusEventHandler FriendStatusEvent;
        #endregion

        #region Public methods
        /// <summary>
        /// Initialize cBot
        /// </summary>
        public void Initialize()
        {
            if (_accessControl == null)
            {
                _accessControl = new AccessControl();
                _moduleManager = new ModuleManager();
                LoadPlugins();
                LoadConfiguration();
            }
        }

        /// <summary>
        /// Connect to server
        /// </summary>
        public void Connect()
        {
            _chat = new Chat(_serverAddress, _port, _userName, _password, _characterName);
            _chat.ChannelJoinEvent += _chat_ChannelJoinEvent;
            _chat.PrivateMessageEvent += _chat_TellEvent;
            _chat.ChannelMessageEvent += _chat_ChannelMessageEvent;
            _chat.FriendStatusEvent += _chat_FriendStatusEvent;
            _chat.Connect();
        }

        /// <summary>
        /// Sends a message to guild channel
        /// </summary>
        /// <param name="message">Message to be send</param>
        public void SendGuildMessage(String message)
        {
            _chat.SendChannelMessage(_guildChannelID, message);
        }

        /// <summary>
        /// Sends a tell to a player
        /// </summary>
        /// <param name="characterName">Character name of the player</param>
        /// <param name="message">Message to be send</param>
        public void SendPrivateMessage(String characterName, String message)
        {
            _chat.SendPrivateMessage(characterName, message);
        }

        /// <summary>
        /// Send a message to a player or to guild channel
        /// </summary>
        /// <param name="message">Message to be send</param>
        /// <param name="sendTo">Character name of the player if message is send by tell</param>
        /// <param name="toChannel">Enum to select if tell or guild message</param>
        public void SendMessage(String message, String sendTo, ListenToEnum toChannel)
        {
            switch (toChannel)
            {
                case ListenToEnum.Guild:
                    SendGuildMessage(message);
                    break;
                case ListenToEnum.Tell:
                    SendPrivateMessage(sendTo, message);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        /// <summary>
        /// Add friend in friend list
        /// </summary>
        /// <param name="friendName">Name of the character</param>
        public void AddFriend(string friendName)
        {
            _chat.SendFriendAdd(friendName);
        }

        /// <summary>
        /// Remove friend from friend list
        /// </summary>
        /// <param name="friendName">Name of the character</param>
        public void RemoveFriend(string friendName)
        {
            _chat.SendFriendRemove(friendName);
        }

        /// <summary>
        /// Disconnect cBot
        /// </summary>
        public void Disconnect()
        {
            _chat.Disconnect();
        }
        #endregion

        #region Private methods
        private static void LoadPlugins()
        {
            string[] files = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll");
            foreach (String file in files)
            {
                try
                {
                    Assembly assembly = Assembly.LoadFile(file);
                    foreach (Type type in assembly.GetTypes())
                    {
                        if (!type.IsClass || type.IsNotPublic) continue;
                        Type[] types = type.GetInterfaces();
                        if (((IList)types).Contains(typeof(IModule)))
                        {
                            IModule module = (IModule)Activator.CreateInstance(type);
                            Console.WriteLine(_cBotContext._moduleManager.AddModule(module)
                                ? String.Format("{0} succefully loaded", module.Name)
                                : String.Format("{0} not loaded", module.Name)
                                );
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
            }
        }
        private void LoadConfiguration()
        {
            cBotConfiguration cBotConfig = cBotConfiguration.Read("cBotConfiguration.xml");
            _serverAddress = cBotConfig.Server;
            _userName = cBotConfig.Username;
            _port = cBotConfig.Port;
            _password = cBotConfig.Password;
            _characterName = cBotConfig.Character;
        }
        private void HandleMessage(String fromWho, String message, ListenToEnum fromWhere)
        {
            String[] strings = message.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries);
            String command = strings[0];
            String[] args = new String[0];

            if (strings.Length > 1)
            {
                List<String> auxArgs = new List<string>();
                auxArgs.AddRange(strings);
                args = auxArgs.GetRange(1, auxArgs.Count - 1).ToArray();
            }

            ICommand cmd = _moduleManager.GetCommand(command, fromWhere);

            //Who who = new Who();
            //FriendStatusEventArgs guildInfo = who.WhoIs(fromWho);

            if (cmd != null && _accessControl.IsAllowed(fromWho, cmd.AccessLevel))
            {
                cmd.ExecuteCommand(args, fromWho, fromWhere);
            }
            else
            {
                String returnMessage = "You are not allowed to run that command";
                if (cmd == null) returnMessage = "Command not found. Try Help for view all command available";
                switch (fromWhere)
                {
                    case ListenToEnum.Guild:
                        SendGuildMessage(returnMessage);
                        break;
                    case ListenToEnum.Tell:
                        SendPrivateMessage(fromWho, returnMessage);
                        break;
                }

            }
        }
        #endregion

        #region Subcribed events
        private void _chat_ChannelJoinEvent(Chat chat, ChannelJoinEventArgs e)
        {
            if (e.Type == ChannelType.Guild)
            {
                _guildChannelID = e.ID;
            }
        }

        private void _chat_TellEvent(Chat chat, PrivateMessageEventArgs e)
        {
            if (e.Outgoing) return;
            try
            {
                HandleMessage(e.Character, e.Message, ListenToEnum.Tell);
            }
            catch (Exception z)
            {
                SendPrivateMessage(e.Character, z.Message);
            }
        }

        private void _chat_FriendStatusEvent(Chat chat, FriendStatusEventArgs e)
        {
            if (FriendStatusEvent != null)
            {
                FriendStatusEvent(chat, e);
            }
        }

        void _chat_ChannelMessageEvent(Chat chat, ChannelMessageEventArgs e)
        {
            try
            {
                if (!e.Message[0].Equals('!')) return;
                String message = e.Message.Substring(1, e.Message.Length - 1);
                HandleMessage(e.Character, message, ListenToEnum.Guild);
            }
            catch
            {
                SendGuildMessage("Error executing command.");
            }
        }
        #endregion
    }
}